package binding

import (
	"encoding/json"
	"errors"
	"gitee.com/lstack_light/go-light/internal/pkg/utils"
	"io"
	"net/http"
	"reflect"
	"strings"
)

type RequestParamType string

const (
	queryParam    RequestParamType = "query"
	uriParam      RequestParamType = "uri"
	headerParam   RequestParamType = "header"
	formDataParam RequestParamType = "form-data"
	formFileParam RequestParamType = "form-file"
)

var reqTypes = []RequestParamType{queryParam, uriParam, headerParam, formDataParam, formFileParam}

type Bind struct {
	*http.Request
	SourceData []reflect.Value
}

func (b *Bind) BindParams(uriMap utils.Map) error {
	if nil == b.Request {
		return errors.New("请求异常，无法进行参数绑定！")
	}
	if 0 == len(b.SourceData) {
		return nil
	}
	var err error
	if strings.HasPrefix(b.Request.Header.Get("Content-Type"), "multipart/form-data") {
		err = b.Request.ParseMultipartForm(10 << 20)
		if nil != err {
			return err
		}
	}
	for _, data := range b.SourceData {
		source := data.Elem()
		sourceType := source.Type()
		fields := sourceType.NumField()
		if reflect.ValueOf(b.Body).IsValid() {
			newStruct := reflect.New(sourceType).Elem()
			reader := b.Body.(io.Reader)
			err = json.NewDecoder(reader).Decode(newStruct.Addr().Interface())
			if nil != err && err.Error() == "EOF" {
				err = nil
			}
			if nil != err {
				return err
			}
			source.Set(reflect.ValueOf(newStruct.Interface()))
		}
		for _, rt := range reqTypes {
			for i := 0; i < fields; i++ {
				name := sourceType.Field(i).Tag.Get(string(rt))
				if 0 < len(name) {
					var value interface{}
					switch rt {
					case uriParam:
						value = uriMap.Get(name)
					case queryParam:
						value = b.Request.URL.Query().Get(name)
					case headerParam:
						value = b.Request.Header.Get(name)
					case formDataParam:
						value = b.FormValue(name)
					case formFileParam:
						_, value, err = b.FormFile(name)
						if nil != err && http.ErrMissingFile != err {
							return err
						}
					}
					if reflect.ValueOf(value).IsValid() {
						if nil != err {
							err = errors.New("[" + string(rt) + ":" + name + "], can't convert " +
								reflect.ValueOf(value).Type().String() + " to " + sourceType.Field(i).Type.String())
							return err
						}
						if sourceType.Field(i).Type.Kind() != reflect.Struct {
							value, err = utils.ConvertTo(sourceType.Field(i).Type, value)
						}
						source.Field(i).Set(reflect.ValueOf(value))
					}
				}
			}
		}
	}
	return err
}
